package objects;

import com.sun.j3d.loaders.Scene;
import com.sun.j3d.utils.picking.PickTool;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Group;
import javax.media.j3d.Morph;
import javax.media.j3d.Node;
import javax.media.j3d.SceneGraphObject;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Vector3d;
import org.jdesktop.j3d.loaders.vrml97.VrmlLoader;
import start.Config;

/**
 * Erstellt die Uhr aus der VRML-Datei und
 * bietet möglichkeiten zur Interaktion
 * 
 * @version 1.0
 * @author Imanuel Rohlfing
 * @author Dennis Wittkoetter
 */
@SuppressWarnings("deprecation")
public class Clock {
    /**
     * Aktuelle Stunde
     */
    private int currentHour;
    /**
     * AKtuelle Minute
     */
    private int currentMinute;
    /**
     * Uhr steht / laeuft
     */
    private boolean isRunning;
    /**
     * BranchGroup für die Uhr-Elemente
     */
    private BranchGroup clockBG;
    /**
     * Zeiger
     */
    private Hands hands;
    /**
     * Pendel
     */
    private Pendulum pendulum;
    /**
     * Aktuelle Zeigergeschwindigkeitsstufe
     */
    private int speed;
    
    
    /**
     * Konstruiert die Uhr aus VRML-Datei und fuegt sie der BranchGroup hinzu.
     *
     * @param wrlName	Name der *.wrl-Datei
     */
    @SuppressWarnings("rawtypes")
    public Clock(String wrlName) {
    	this.speed = 1;
        this.currentHour = 0;
        this.currentMinute = 0;
        this.clockBG = new BranchGroup();

        VrmlLoader vrml = new VrmlLoader();
        Scene scene = null;
        try {
            java.net.URL url = ClassLoader.getSystemResource( wrlName );
            scene = vrml.load( url );
        } catch (Exception e) {
            System.out.println("Szene kann nicht geladen werden");
        }

        Transform3D translate = new Transform3D();
        translate.set(new Vector3d(-0.1, 0.0, 0.0));

        TransformGroup clockTG = new TransformGroup();
        clockTG.setTransform(translate);
        clockTG.addChild(scene.getSceneGroup());
        clockBG.addChild(clockTG);

        Hashtable namedObjects = scene.getNamedObjects();

        /*********************** Debug *************************/
        if (Config.debug) {
            System.out.println("Benannte Objekte in VRML-Datei:");
            Enumeration e = namedObjects.keys();
            int objAmount = 1;
            while (e.hasMoreElements()) {
                System.out.println(objAmount + ": " + e.nextElement());
                objAmount++;
            }
            System.out.println("-----------------------------");
        }
        /******************************************************/
        
        /*
         * UserData der Objekte rekursiv setzen. 
         * Koennen so per Picking
         * identifiziert werden
         */
        Enumeration enumValues = namedObjects.elements();
        Enumeration enumKeys = namedObjects.keys();

        if (Config.debug) {
            System.out.print("Schreibe VRML-UserData...");
        }
        if (enumValues != null) {
            while (enumValues.hasMoreElements()) {
                Object value = enumValues.nextElement();
                Object key = enumKeys.nextElement();

                if (Config.debug) {
                    System.out.print(".");
                }
                recursiveSetUserData(value, key);
            }
        }
        if (Config.debug) {
            System.out.println();
        }

        //Zeiger- und Pendel-Objekte identifizieren
        Object hours = scene.getNamedObjects().get("Stundenzeiger");
        Object minutes = scene.getNamedObjects().get("Minutenzeiger");
        Object pend = scene.getNamedObjects().get("Pendel");

        try {
            //Zeiger-Objekt anlegen
            if ((hours instanceof TransformGroup) && (minutes instanceof TransformGroup)) {
                hands = new Hands((TransformGroup) hours, (TransformGroup) minutes);
            }

            //Pendel-Objekt anlegen
            if (pend instanceof TransformGroup) {
                pendulum = new Pendulum((TransformGroup) pend);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Zeiger oder Pendel konnten nicht geladen werden.");
        }

        //Uhr auf Systemzeit einstellen
        this.setSystemTime();
    }

    /**
     * Liefert Status der Uhr (Uhr gestartet oder gestoppt).
     * @return	Boolean 'true' wenn Uhr laeuft, 'false' wenn nicht
     */
    public boolean isRunning() {
        return isRunning;
    }

    /**
     * Liefert Uhrzeiger
     * @return
     */
    public Hands getHands() {
        return hands;
    }

    /**
     * Liefert Pendel
     * @return
     */
    public Pendulum getPendulum() {
        return pendulum;
    }

    /**
     * Liefert BranchGroup
     * @return
     */
    public BranchGroup getBranchGroup() {
        return clockBG;
    }

    /**
     * Startet die Zeiger- und Pendelanimationen der Uhr.
     * @see Pendulum
     * @see Hands
     */
    public void start() {
    	hands.setMovementForward();
    	this.setSpeedLevel(1);
        pendulum.start();
        hands.start();
        isRunning = true;
        //System.out.println("Uhr gestartet");
    }

    /**
     * Stoppt die Zeiger- und Pendelanimationen der Uhr.
     * @see Pendulum
     * @see Hands
     */
    public void stop() {
        pendulum.stop();
        hands.stop();
        isRunning = false;
        //System.out.println("Uhr gestoppt");
    }

    /**
     * Methode zum Setzen der UserData-Werte der Objekte im Baum des SceneGraph.
     * Ruft sich rekursiv selbst auf.
     *
     * @param value
     * @param key
     * 
     * @see interaction.Pick
     */
    @SuppressWarnings("rawtypes")
    private void recursiveSetUserData(Object value, Object key) {
        if (value instanceof SceneGraphObject != false) {
            // UserData des Objekts setzen
            SceneGraphObject sg = (SceneGraphObject) value;
            sg.setUserData(key);

            //Gruppe auswaehlen
            if (sg instanceof Group) {
                Group g = (Group) sg;

                //ueber Kinder laufen
                Enumeration enumKids = g.getAllChildren();

                while (enumKids.hasMoreElements()) {
                    recursiveSetUserData(enumKids.nextElement(), key);	//rekursiver Audruf
                }
            } else if (sg instanceof Shape3D || sg instanceof Morph) {
                PickTool.setCapabilities((Node) sg, PickTool.INTERSECT_FULL);
            }
        }
    }
        
    /**
     * Setzt das Intervall, in dem der Minutenzeiger um eine Position versetzt wird.
     * @param s		Wert in Millisekunden
     * 
     * @see Hands
     */
    private void setHandsSpeed(int s) {
    	hands.setTimerDelay(s);
    }
    
    /**
     * Setzt die Bewegungsgeschwindigkeit der Zeiger auf die angegebene Stufe.
     * Stufe 1: Standard-Geschwindigkeit
     * Stufe 2: 60-fache Geschwindigkeit
     * Stufe 3: 60^2-fache Geschwindigkeit
     * Stufe 4: 6*60^2-fache Geschwindigkeit
     * 
     * @param l	Geschwindigkeitsstufe
     */
    public void setSpeedLevel(int l) {
    	switch(l) {
    	case 2:	
    		this.speed = l;
    		this.setHandsSpeed(Config.defaultHandsSpeed/60); 
    		break;
    	case 3:	
    		this.speed = l;
    		this.setHandsSpeed(Config.defaultHandsSpeed/3600);	
    		break;
    	case 4:
    		this.speed = l;
    		this.getHands().setRotDegree(6);
    		break;
    	default:	//l==1 || l>=5 
    		this.speed = 1;
    		this.getHands().setRotDegree(Config.defaultHandsDegree);
    		this.setHandsSpeed(Config.defaultHandsSpeed);
    	}
    }
    
    /**
     * Liefert das aktuelle Geschwindigkeitslevel der Zeigerbewegung.
     * @return
     */
    public int getSpeedLevel() {
    	return speed;
    }

    /**
     * Stellt die Uhr auf die angegebene Zeit ein.
     *
     * @param hour	einzustellende Stunde (im Bereich 0 bis 23)
     * @param min	einzustellende Minute (im Bereich 0 bis 59)
     * 
     * @see Hands
     */
    public void setTime(int hour, int min) {
        if (min >= 59 || min < 0) {
            System.out.println("Ungueltiger Wert fuer Stunde");
            return;
        }
        if (hour >= 23 || hour < 0) {
            System.out.println("Ungueltiger Wert fuer Minute");
            return;
        }

        //Stundenformat anpassen
        if (hour > 12) {
            hour -= 12;
        }

        //Minutenwerte errechnen
        int newTime = (hour * 60) + min;		//neu einzustellende Zeit
        int curTime = (hands.getHour() * 60) + hands.getMinute();	//aktuelle Zeit
        int deltaTime = (newTime - curTime);	//Differenz der beiden Zeitwerte

        if (Config.debug)
            System.out.println("Setze Uhrzeit auf " + hour + ":" + min);

        /*
         * Uhr einstellen
         * Die Zeiger werden dabei um den Differenzwert verschoben.
         */
        for (int i=0; i < deltaTime; i++) {
            hands.moveBothHands(6);
        }
    }

    /**
     * Stellt die Uhr auf die aktuelle Systemzeit ein.
     */
    public void setSystemTime() {
        Date time = new Date();				//aktuelles Datum und Uhrzeit
        currentHour = time.getHours();		//aktuelle Stunde holen
        currentMinute = time.getMinutes();	//aktuelle Minute holen

        //Uhrzeit setzen
        this.setTime(currentHour, currentMinute);
    }
}
